function gadget:GetInfo()
  return {
    name      = "Range Circles and Arcs",
    desc      = "Draws extra range circles and field of fire arcs",
    author    = "Rafal",
    date      = "Dec 2011",
    license   = "GNU GPL, v2 or later",
    layer     = 0,
    enabled   = true  --  loaded by default?
  }
end

--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- COMMON

-- CONFIG

--local drawRangeCircles = true
local drawFireArcs = true

--from cmdcolors.txt:
--rangeAttack          1.0  0.3  0.3  0.7
--rangeRadar           0.3  1.0  0.3  0.7
--rangeSonar           0.3  0.3  1.0  0.7

local radarColor = { 0.3, 1.0, 0.3, 0.8 }
local sonarColor = { 0.2, 0.2, 1.0, 1.0 }
local weaponRangeAlpha = 0.8
local fireArcAlpha = 1.0

local rangeColors = {
  default    = { 1.0, 0.3, 0.3 },
  arty       = { 1.0, 0.6, 0.3 },
  antiair    = { 0.2, 1.0, 1.0 },
  water      = { 0.2, 0.2, 1.0 },
  underwater = { 0.2, 0.2, 0.7 },
}

rangeColors["ground"      ] = rangeColors["default"]
rangeColors["ground +"    ] = rangeColors["default"]
rangeColors["multipurpose"] = rangeColors["antiair"]

local lineStippleFactor = 1.5
local lineStipplePattern = 0x00FF
local rangeCircleDivs = 40
local fireArcDivs = 20
local fireArcRadius = 10
local fireArcLength = 50
local fireArcLineWidth = 2.0

-- COMMON
--------------------------------------------------------------------------------
if (gadgetHandler:IsSyncedCode()) then
--------------------------------------------------------------------------------
-- SYNCED

local spGetUnitDefID     = Spring.GetUnitDefID
local spGetUnitPieceList = Spring.GetUnitPieceList
--local spGetScriptEnv = Spring.UnitScript.GetScriptEnv  --not available before Initialize
--local spCallAsUnit   = Spring.UnitScript.CallAsUnit
local spEcho         = Spring.Echo

--------------------------------------------------------------------------------

local fireArcDataSynced = {}  
_G.fireArcDataSynced = fireArcDataSynced  --make it visible from unsynced


local function CreateFireArcDataSynced (unitID, udID)
  local data = {}
  local numArcs = 0

  local weapons = UnitDefs[udID].weapons
  if (type(weapons) == "table") then
    local aimWeaponCalls = {}
    local scriptDef = GG.ScriptGlobal.ScriptDefs[udID]
    local sdWeapons = scriptDef and scriptDef.weapons or nil
    
    for i, w in ipairs(weapons) do
      local maxAngleDif = w.maxAngleDif or -1
          
      if (maxAngleDif ~= -1) then
        numArcs = numArcs + 1
        if (sdWeapons and sdWeapons[i] and sdWeapons[i].turretPiece) then
          data[numArcs] = { pieceNum = sdWeapons[i].turretPiece, }
        else
          data[numArcs] = { pieceNum = -1, }  -- -1
          aimWeaponCalls[i] = data[numArcs]
        end  
      end
    end

    if (numArcs > 0 and next(aimWeaponCalls) and Spring.UnitScript.GetScriptEnv) then
      local env = Spring.UnitScript.GetScriptEnv(unitID)
      if (env and env.script) then 
        local spCallAsUnit = Spring.UnitScript.CallAsUnit
        local numPieces = #(spGetUnitPieceList(unitID))
        local envScript = env.script
        local func = envScript["AimFromWeapon"]
                
        for i, arc in pairs(aimWeaponCalls) do
          local funcI = envScript["AimFromWeapon" .. i]
          
          local piece = nil
          if (funcI) then 
            piece = spCallAsUnit(unitID, funcI) 
          elseif (func) then
            piece = spCallAsUnit(unitID, func, i)  --check piece id
          end
          if (piece and 1 <= piece and piece <= numPieces) then
            arc.pieceNum = piece
          end
        end
      end
    end
  end  
  fireArcDataSynced[udID] = (numArcs > 0) and data or true
end

--------------------------------------------------------------------------------

function gadget:Initialize()
	local units = Spring.GetAllUnits()
	for _,unitID in ipairs(units) do
		local udID = spGetUnitDefID(unitID)
		gadget:UnitCreated(unitID, udID)  --, nil, -1)
	end
end

function gadget:UnitCreated(unitID, unitDefID, teamID, builderID)   
  GG.ScriptGlobal = GG.ScriptGlobal or {}
  GG.ScriptGlobal.ScriptDefs = GG.ScriptGlobal.ScriptDefs or {} 

  if (not fireArcDataSynced[unitDefID]) then
    CreateFireArcDataSynced(unitID, unitDefID)
  end
end

-- SYNCED
--------------------------------------------------------------------------------
else
--------------------------------------------------------------------------------
-- UNSYNCED

local spGetActiveCommand       = Spring.GetActiveCommand
local spGetSelectedUnitsSorted = Spring.GetSelectedUnitsSorted
local spIsUnitSelected         = Spring.IsUnitSelected
local spGetUnitViewPosition    = Spring.GetUnitViewPosition
local spGetUnitHeading         = Spring.GetUnitHeading
local spGetUnitVectors         = Spring.GetUnitVectors
local spGetUnitPieceInfo       = Spring.GetUnitPieceInfo
local spGetUnitLosState        = Spring.GetUnitLosState
local spGetUnitDefID           = Spring.GetUnitDefID
local spGetMyAllyTeamID        = Spring.GetMyAllyTeamID
local spGetMouseState          = Spring.GetMouseState
local spTraceScreenRay         = Spring.TraceScreenRay
local spEcho                   = Spring.Echo
local glLineWidth              = gl.LineWidth
local glLineStipple            = gl.LineStipple
local glSmoothing              = gl.Smoothing
local glColor                  = gl.Color
local glPopMatrix              = gl.PopMatrix
local glPushMatrix             = gl.PushMatrix
local glTranslate              = gl.Translate
local glRotate                 = gl.Rotate
local glShape                  = gl.Shape
local glCreateList             = gl.CreateList
local glCallList               = gl.CallList
local glDeleteList             = gl.DeleteList
local glDrawGroundCircle       = gl.DrawGroundCircle
local GL_LINE_STRIP            = GL.LINE_STRIP
local CMD_ATTACK               = CMD.ATTACK

local floor                    = math.floor
local ceil                     = math.ceil
local deg                      = math.deg
local rad                      = math.rad
local sin                      = math.sin
local cos                      = math.cos
local acos                     = math.acos
local atan2                    = math.atan2

--------------------------------------------------------------------------------

local fireArcDivsAngle = rad(360 / fireArcDivs)
local defsToCircles = { ["n"] = {} }
local defsToFireArcData = { ["n"] = true }
local fireArcLists = {}

--local SQUARE_SIZE = 8
local radarMipLevel = 3
local radarDiv = Game.squareSize * (2 ^ radarMipLevel)

local function checkColorTable (color, hasAlpha, colorName)
  if (
    type(color) ~= "table" or
    type(color[1]) ~= "number" or
    type(color[2]) ~= "number" or
    type(color[3]) ~= "number" or      
    (type(color[4]) ~= "number" and hasAlpha)
  ) then
    spEcho('[gui_range_circles.lua] Error: Wrong ' .. colorName .. ' range color')  
  end
end


checkColorTable (radarColor, true, 'radar')
checkColorTable (sonarColor, true, 'sonar')
for weaponType, color in pairs(rangeColors) do
  checkColorTable (color, false, 'weapontype "' .. weaponType .. '"')
end


for udID, ud in ipairs(UnitDefs) do
  local sensorCircles = {}
  if (ud.sonarRadius >= radarDiv) then
    local realRadius = floor(ud.sonarRadius / radarDiv) * radarDiv;
    sensorCircles[realRadius] = sonarColor
  end
  if (ud.radarRadius >= radarDiv) then
    local realRadius = floor(ud.radarRadius / radarDiv) * radarDiv;
    sensorCircles[realRadius] = radarColor
  end
  
  local weaponCircles = {}
  if (ud.myCustomParams.rangeCircles) then 
    for range, weaponType in pairs(ud.myCustomParams.rangeCircles) do
      local color = rangeColors[weaponType:lower()]
      if (color) then
        weaponCircles[range] = color
      else
        spEcho('[gui_range_circles.lua] Error: UnitDef "' .. ud.name .. '" - invalid range circle type "' .. weaponType .. '"')
      end
    end
  end
  
  if (next(sensorCircles) or next(weaponCircles)) then
    defsToCircles[udID] = {
      sensors = sensorCircles,
      weapons = weaponCircles,
    }  
  else
    defsToCircles[udID] = {}
  end  
  
  --defsToCircles[udID].defaultRange = ud.maxWeaponRange 
  
  local weapons = ud.weapons
  if (type(weapons) == "table" and weapons[1]) then
    local wd = WeaponDefs[weapons[1].weaponDef]
    local range = wd and wd.range or nil
    if (range) then
      defsToCircles[udID].defaultRange = range
    end
  end
end


local HEADING_TO_DEG = 360 / 65536
local function HeadingToDegrees (heading)
	return heading * HEADING_TO_DEG
end

local function glColor4fv (color, alpha)
  glColor(color[1], color[2], color[3], alpha)
end

local function DrawFireArc (maxAngleDif)
  --draw arc edges
  local halfAngle = acos(maxAngleDif)  --rad(0.5 * arcAngle)
  local angleSin = sin(halfAngle)
  local angleCos = maxAngleDif
  local x = angleSin * fireArcLength
  local z = angleCos * fireArcLength
	local vertices = {
		{v = {-x, 0, z}},
		{v = { 0, 0, 0}},
		{v = { x, 0, z}},
	}
	glShape(GL_LINE_STRIP, vertices)
  
  --draw arc
  local numDivs = ceil(halfAngle / fireArcDivsAngle)
  local middleVertex = 1 + numDivs
  x = angleSin * fireArcRadius
  z = angleCos * fireArcRadius  
  vertices = {
		[1]               = {v = {-x, 0, z}},
		[middleVertex]    = {v = { 0, 0, fireArcRadius}},
		[1 + numDivs * 2] = {v = { x, 0, z}},
	}
  for i = 1, (numDivs - 1) do
    local theta = i * fireArcDivsAngle
    x = sin(theta) * fireArcRadius
    z = cos(theta) * fireArcRadius
    vertices[middleVertex - i] = {v = {-x, 0, z}}
    vertices[middleVertex + i] = {v = { x, 0, z}}
  end
  
  glShape(GL_LINE_STRIP, vertices)
end


local function CreateFireArcDisplayList (maxAngleDif)
  if (not fireArcLists[maxAngleDif]) then
    fireArcLists[maxAngleDif] = glCreateList(DrawFireArc, maxAngleDif)
  end  
end

local function DrawFireArcAtPos (xPos, yPos, zPos, rotation, maxAngleDif)
  glPushMatrix()
  
  glTranslate(xPos, yPos, zPos)
  glRotate(rotation, 0.0, 1.0, 0.0)
  --glScale(range, range, range)
  
  glCallList(fireArcLists[maxAngleDif])
  
  glPopMatrix()
end

local function CreateFireArcDataUnsynced (unitID, udID)
  local data = {}
  local numArcs = 0

  local weapons = UnitDefs[udID].weapons
  if (type(weapons) == "table") then
    local syncedData = SYNCED.fireArcDataSynced[udID]
    local syncedDataOK = (type(syncedData) == "table")
    
    for i, w in ipairs(weapons) do    
      local maxAngleDif = w.maxAngleDif or -1
          
      if (maxAngleDif ~= -1) then 
        CreateFireArcDisplayList(maxAngleDif) 
        
        numArcs = numArcs + 1 
        local wd = WeaponDefs[w.weaponDef]
        local rangeColor = wd and wd.myCustomParams.rangeColor:lower() or "default"
        local weaponPiece = (syncedDataOK and syncedData[numArcs] and syncedData[numArcs].pieceNum) or -1  -- -1
        local offset = nil
        if (weaponPiece ~= -1) then
          local info = spGetUnitPieceInfo(unitID, weaponPiece)
          offset = info and info.offset or nil
          --local x,y,z = Spring.GetUnitPiecePosition(unitID, weaponPiece)
          --spEcho("Offset: " .. offset[1] .. " " .. offset[2] .. " " .. offset[3])
          --spEcho("PiecePos: " .. x .. " " .. y .. " " .. z)
        end  

        data[numArcs] = {
          color = rangeColors[rangeColor] or rangeColors.default,
          angleDif = maxAngleDif,
          mainDirAngle = deg(atan2(w.mainDirX, w.mainDirZ)),
          pieceOffset = offset,
        }  
      end  
    end
  end
  defsToFireArcData[udID] = (numArcs > 0) and data or true
end

--------------------------------------------------------------------------------

function gadget:Shutdown()
  for _, list in pairs(fireArcLists) do
    glDeleteList(list)
  end
end


function gadget:DrawWorld()
  local _, cmd = spGetActiveCommand()
  local attack = (cmd == CMD_ATTACK)

  --glDepthTest(true)
  glSmoothing(false, true, false)
  glLineWidth(1.0)

  -- draw range circles for selected units
  for udID, units in pairs(spGetSelectedUnitsSorted()) do
    local circlesData = defsToCircles[udID]
    if (circlesData.sensors) then
      local x = {}
      local y = {}
      local z = {}
      for i, unitID in ipairs(units) do
        x[i], y[i], z[i] = spGetUnitViewPosition(unitID, true)
      end  

      if (not attack) then 
        glLineStipple(lineStippleFactor, lineStipplePattern)
        for range, color in pairs(circlesData.sensors) do  -- draw sensor circles 
          glColor4fv(color, color[4])
        
          for i = 1, #units do
            glDrawGroundCircle(x[i], y[i], z[i], range, rangeCircleDivs)
          end  
        end
        glLineStipple(false)
      else  
        for range, color in pairs(circlesData.weapons) do  -- draw weapon circles 
          glColor4fv(color, weaponRangeAlpha)
          
          for i = 1, #units do
            glDrawGroundCircle(x[i], y[i], z[i], range, rangeCircleDivs)
          end  
        end
      end  
    end
  end

  
  -- draw stuff for unit under the cursor
  local mx, my = spGetMouseState()
  local mouseTargetType, mouseTarget = spTraceScreenRay(mx, my) 
  if (mouseTargetType == "unit") then
    local losState = spGetUnitLosState(mouseTarget, spGetMyAllyTeamID())
    
    if (losState and losState.los) then
      local udID = spGetUnitDefID(mouseTarget)
      
      -- draw range circles for unit under the cursor
      local circlesData = defsToCircles[udID]
      --local defaultRange = UnitDefs[udID].maxWeaponRange
      if (circlesData.sensors or circlesData.defaultRange) then
        local x, y, z = spGetUnitViewPosition(mouseTarget, true)
        local selected = spIsUnitSelected(mouseTarget)
          
        if (not selected or attack) then
          if (circlesData.sensors) then
            glLineStipple(lineStippleFactor, lineStipplePattern)
            for range, color in pairs(circlesData.sensors) do  -- draw sensor circles
              glColor4fv(color, color[4])
              glDrawGroundCircle(x, y, z, range, rangeCircleDivs)
            end
            glLineStipple(false)              
          end
        end  
        if (not selected or not attack) then
          if (circlesData.weapons) then
            for range, color in pairs(circlesData.weapons) do  -- draw weapon circles 
              glColor4fv(color, weaponRangeAlpha)
              glDrawGroundCircle(x, y, z, range, rangeCircleDivs)
            end
          end
          if (circlesData.defaultRange) then  -- draw default weapon circle 
            glColor4fv(rangeColors.default, weaponRangeAlpha)
            glDrawGroundCircle(x, y, z, circlesData.defaultRange, rangeCircleDivs)  
          end 
        end
      end 
      
      if (drawFireArcs) then
        if (not defsToFireArcData[udID]) then
          CreateFireArcDataUnsynced(mouseTarget, udID)
        end

        -- draw field of fire arcs for unit under the cursor
        if (type(defsToFireArcData[udID]) == "table") then 
          local data = defsToFireArcData[udID]
          local x, y, z = spGetUnitViewPosition(mouseTarget, false)
          local frontVec, upVec, rightVec = spGetUnitVectors(mouseTarget)          
          local pieceX, pieceY, pieceZ

          glLineWidth(fireArcLineWidth)
          for _, arc in ipairs(data) do
            glColor4fv(arc.color, fireArcAlpha)
            
            --if (arc.pieceNum > 0) then
            --  pieceX, pieceY, pieceZ = spGetUnitPiecePosDir(mouseTarget, arc.pieceNum)         
            local offset = arc.pieceOffset
            if (offset) then
              local offX = offset[1]
              local offY = offset[2]
              local offZ = offset[3]
              pieceX = x + offZ * frontVec[1] + offY * upVec[1] + offX * rightVec[1]
              pieceY = y + offZ * frontVec[2] + offY * upVec[2] + offX * rightVec[2]
              pieceZ = z + offZ * frontVec[3] + offY * upVec[3] + offX * rightVec[3]
              --spEcho("Offset: " .. offX .. " " .. offY .. " " .. offZ)
              --spEcho("PiecePos: " .. pieceX .. " " .. pieceY .. " " .. pieceZ)
            else
              pieceX, pieceY, pieceZ = x, y, z
            end
            
            local rotation = HeadingToDegrees(spGetUnitHeading(mouseTarget)) + arc.mainDirAngle
            DrawFireArcAtPos (pieceX, pieceY, pieceZ, rotation, arc.angleDif)  
          end
          glLineWidth(1.0)          
        end
      end
    end
  end  
  
  glSmoothing(false, false, false)
  glColor(1, 1, 1, 1)  
end

-- UNSYNCED
end